package com.cy.stone.framework.web.advice;

import cn.hutool.core.util.CharsetUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.crypto.asymmetric.KeyType;
import cn.hutool.crypto.asymmetric.RSA;
import cn.hutool.crypto.symmetric.AES;
import com.cy.stone.common.annotation.Decrypt;
import com.cy.stone.common.properties.DecryptProperties;
import com.cy.stone.framework.web.advice.DecryptHttpMessage;
import lombok.RequiredArgsConstructor;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.core.MethodParameter;
import org.springframework.http.HttpInputMessage;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.util.StreamUtils;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.RequestBodyAdviceAdapter;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.lang.reflect.Type;
import java.nio.charset.Charset;

/**
 * 请求解密
 * @author Wings
 * @since 2023-12-10
 */
@ControllerAdvice
@ConditionalOnProperty(prefix = "stone.decrypt", name = "enable", havingValue = "true")
@RequiredArgsConstructor
public class DecryptRequestAdvice extends RequestBodyAdviceAdapter {

    private final DecryptProperties decryptProperties;

    @Override
    public boolean supports(MethodParameter methodParameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {
        return true;
    }

    /**
     * 解密逻辑
     * <p>1. 判断请求的controller的类上或者方法上是否有Decrypt注解，如果有，则表示需要解密</p>
     * <p>2. 取得header里的secretKey的值，对其解密，获得AES key</p>
     * <p>3. 根据AES的key对body请求体解密</p>
     */
    @Override
    public HttpInputMessage beforeBodyRead(HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) throws IOException {
        // 如果支持加密消息，进行消息加密
        Decrypt decrypt = supportDecryptRequest(parameter);
        String httpBody = "";
        if (decrypt != null) {
            // 根据密钥解密AES key,再根据AES key解密body消息
            String privateKey = decryptProperties.getPrivateKey();
            RSA rsa = new RSA(privateKey, null);
            byte[] aesKey = rsa.decrypt(inputMessage.getHeaders().getFirst("secretKey"), KeyType.PrivateKey);
            AES aes = new AES("ECB", "PKCS7Padding", aesKey);
            // 解密请求体
            String decryptBody = StreamUtils.copyToString(inputMessage.getBody(), Charset.defaultCharset());
            if (StringUtils.hasLength(decryptBody)) {
                // 解密请求体
                byte[] decryptBodyByte = aes.decrypt(decryptBody);
                httpBody = StrUtil.str(decryptBodyByte, CharsetUtil.CHARSET_UTF_8);
            }
        } else {
            httpBody = StreamUtils.copyToString(inputMessage.getBody(), Charset.defaultCharset());
        }
        // 返回处理后的消息体
        return new DecryptHttpMessage(new ByteArrayInputStream(httpBody.getBytes()), inputMessage.getHeaders());
    }

    /**
     * 是否有解密注解
     *
     * @param methodParameter methodParameter
     * @return true/false
     */
    private Decrypt supportDecryptRequest(MethodParameter methodParameter) {
        // 判断class是否存在注解
        Decrypt decrypt = methodParameter.getContainingClass().getAnnotation(Decrypt.class);
        if (decrypt != null) {
            return decrypt;
        }
        // 判断方法是否存在注解
        return methodParameter.getMethodAnnotation(Decrypt.class);
    }
}
